/*
 * JDaySelector.java
 *
 * Created on 7-Mar-2010.
 */

package org.calendar;



import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.ButtonGroup;
import javax.swing.border.Border;
import javax.swing.BorderFactory;

import java.awt.GridLayout;
import java.awt.Color;
import java.awt.Font;

import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.event.EventListenerList;

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.text.SimpleDateFormat;
import java.text.DateFormatSymbols;


import org.calendar.button.DayButton;
import org.calendar.event.*;



/**
 * This class is a Component for choosing a day.
 * It listens to the model calendar changes.
 *
 * @see CalendarModelListener
 * @see DefaultCalendarModel
 *
 * @author Julie Duvillier
 */

public class JDaySelector
        extends JPanel
        implements CalendarModelListener {
    
    private static final int  WEEK_DISPLAY =6;
    private static final int DAY_IN_WEEK = 7;

    /* model */
    private DefaultCalendarModel model;

    /* locale */
    private Locale locale;

    /* Format of day string */
    private SimpleDateFormat dayFormat;

    /* Calendar to display fields */
    private Calendar calToDisplay;
    private int displayedMonth;
    private int displayedYear;

    /*Selected calendar fields*/
    private Calendar selectedCalendar;
    private int selectedYear;
    private int selectedMonth;
    private int selectedDay;

    private EventListenerList listenersList;

    /* Components */
    private DayButton[][] attribute;
    private ButtonGroup dayGroup;
    private JLabel[] dayLabels;

    /* Decorations */
    private Color dayLabelForeground ;
    private Color dayLabelBackground;
    private Border dayLabelBorder ;
    private Font dayLabelFont;
    private Font dayButtonFont;
    private Font todayButtonFont;
    private Border todayButtonBorder;
    private Color currentMonthForeground;
    private Color otherMonthForeground;
    private boolean areaFilled;
	/**
	 * Components
	 */
	private DayButton[][] dayButtons;


    /**
     *  Create a new JDaySelector Object.
     */
    public JDaySelector() {
        this( new DefaultCalendarModel() );

    }

    /**
     * Create a new JDaySelector Object with a defined model.
     *
     * @param model the calendar model
     */
    public JDaySelector( DefaultCalendarModel model ) {
           this.model = model;
           this.model.addCalendarModelListener( this );
           listenersList = new EventListenerList();
           setLayout( new GridLayout(7, 7) );
           init();
    }

    /**
     * Initialise the fields needed and build the panel components.
     */
    private void init() {
        calToDisplay = this.model.getCalendarToDisplay();
        locale = this.model.getLocale();
        dayFormat  = new SimpleDateFormat("EE");
        dayFormat.setDateFormatSymbols( new DateFormatSymbols(locale) );
        initSelection();
        initDecorations();
        initComponents();
        updateDayNames();
        updateDays();
    }


    /**
     * Initialise the selected calendar
     */
     private void initSelection(){
        selectedCalendar = (Calendar)calToDisplay.clone();
        selectedYear = selectedCalendar.get(Calendar.YEAR);
        selectedMonth = selectedCalendar.get(Calendar.MONTH);
        selectedDay = selectedCalendar.get(Calendar.DATE);
    }

    /**
     * Initialise the decorations fields
     */
    private void initDecorations(){
        dayLabelForeground = Color.DARK_GRAY;
        dayLabelBorder = BorderFactory.createMatteBorder( 0, 0, 1, 0,
                                                         Color.LIGHT_GRAY );
        dayLabelFont = new Font("Tahoma",0,11);
        dayButtonFont = new Font("Tahoma",0,9);
        todayButtonFont = new Font("Tahoma",Font.BOLD,9);
        todayButtonBorder = BorderFactory.createTitledBorder("");
        currentMonthForeground = Color.BLACK;
        otherMonthForeground = Color.gray;
        areaFilled = false;
    }


    /**
     * Initialize the basics layouts and components of the day chooser
     * panel with the defaults settings.
     */
    private void initComponents() {

        /*
         * Create the week day Labels
         * Set text alignement and add them to the panel.
         */

        dayLabels = new JLabel[ DAY_IN_WEEK ];
        for ( int i = 0 ; i < DAY_IN_WEEK ; i++ ) {
            dayLabels[i] = new JLabel() ;
            dayLabels[i].setHorizontalAlignment(JLabel.CENTER);
            add(dayLabels[i]);
        }

        /*
         * Create a button for each day
         * 6 week (row) / 7 day per week (slots).
         */
        dayButtons = new DayButton[ WEEK_DISPLAY ][ DAY_IN_WEEK ];
        dayGroup = new ButtonGroup();
        for ( int week = 0 ; week < WEEK_DISPLAY ; week ++ ) {
            for ( int day =0 ; day < DAY_IN_WEEK ; day++ ){
                dayButtons[ week ][ day ] = new DayButton();
                dayButtons[ week ][ day ].addItemListener( new DayListener() );
                 dayButtons[week][day].setFont(dayButtonFont);
                 dayButtons[week][day].setContentAreaFilled(areaFilled);
                add( dayButtons[ week ][ day ] );
                dayGroup .add( dayButtons[ week ][ day ] );

            }
        }
    }



    private void updateDayNames(){
        Calendar cal;       // temporary calendar
        int firstDay;       // first day of a week (sunday or monday)
        Date date;          // date to get days format
        String dayStr;      // day of a week string : monday/mon...

        /*
         * Set the week day Labels text. To get them in the proper
         * order we use a temporary Calendar and set his date where
         * the day is equal as the first day of the week of the
         * calendar to display.
         */

        firstDay = calToDisplay.getFirstDayOfWeek();
        cal = Calendar.getInstance();
        cal.setTime(  new Date() );

        while ( cal.get(Calendar.DAY_OF_WEEK) != firstDay ) {
            cal.add( Calendar.DATE, 1 );
        }

        for ( int i = 0 ; i < DAY_IN_WEEK ; i++ ) {
            date = cal.getTime();
            dayStr = dayFormat.format( date ).substring( 0, 2 );
            dayStr = Character.toUpperCase(dayStr.charAt(0))
                         + dayStr.substring(1);
            dayLabels[i].setText(dayStr);
            dayLabels[i].setForeground( dayLabelForeground );
            dayLabels[i].setBorder( dayLabelBorder );
            dayLabels[i].setFont(dayLabelFont);
            cal.add( Calendar.DATE, 1 );
        }
    }
    /**
     * Update the day chooser panel components with the calendar to
     * display.
     */
    private void updateDays() {
        int today;          // real date day
        int month;          // real date month
        int year;           // real date year
        Calendar cal;       // temporary calendar to determine where day 1 goes
        int dayOfWeek;      // day of the week
        int firstDay;       // first day of the current month
        int monthDays;      // number of days in the curent month
        int week;           // week row [1-6]
        int daySlot;        // day slots position (7 per week row )
        int nextDay;        // day of next month.
        int lastMonthDays;  // number of days last month
        int dayToStart;     // day of last month to start filling first slots with.


        displayedMonth = calToDisplay.get(Calendar.MONTH);
        displayedYear = calToDisplay.get( Calendar.YEAR);

        // Get real date fields
        cal = Calendar.getInstance(java.util.Locale.US);
        today = cal.get(Calendar.DATE );
        month = cal.get( Calendar.MONTH );
        year = cal.get( Calendar.YEAR);

        // Get the calendar to display
        cal = (Calendar)calToDisplay.clone();
        cal.set(Calendar.DAY_OF_MONTH, 1);

        dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        firstDay = cal.getFirstDayOfWeek();
        monthDays = cal.getActualMaximum(Calendar.DATE);

        /*
         * If the first day of the month = first day of the week
         * start to fill slots with current month days at week 2
         */
        week = ( dayOfWeek == firstDay )? 1 :0;

        /* Determine how many slot there is before current month first day  */
        for ( daySlot = 0 ; daySlot < DAY_IN_WEEK ; daySlot++ ) {
           int curDay = ( ( firstDay - 1 ) + daySlot ) % 7 +1;
           if ( curDay == dayOfWeek ) {
                break;
           }
        }

        /* Fill Slots with current month days number */
        for( int day = 1 ; day <= monthDays ; day++ ) {
            dayButtons[week][daySlot].setText( Integer.toString( day ) );
            dayButtons[week][daySlot].setCurrentMonth( true );
            dayButtons[week][daySlot].setForeground( currentMonthForeground );

            /*
             * If this day is the selected date select it
             * Otherwise deselect it
             */
            if ( (day == selectedDay)
                      && (displayedMonth == selectedMonth)
                      && (displayedYear == selectedYear) ) {
                dayButtons[week][daySlot].setSelected(true);
                dayButtons[week][daySlot].setContentAreaFilled( ! areaFilled );
            } else {
                dayButtons[week][daySlot].setSelected(false);
                dayButtons[week][daySlot].setContentAreaFilled(areaFilled);
            }

            /*
             * If this day is today set is as today and add his decorations
             * Otherwise set decorations to default ones
             */
            if ( (day == today)
                      && (displayedMonth == month)
                      && (displayedYear == year)  ) {
                dayButtons[week][daySlot].setToday(true);
                dayButtons[week][daySlot].setFont( todayButtonFont );
                dayButtons[week][daySlot].setBorder(todayButtonBorder);

                if( dayButtons[week][daySlot].isSelected() ){
                    dayButtons[week][daySlot].setBorderPainted(false);
                } else {
                    dayButtons[week][daySlot].setBorderPainted(true);
                }
                
            } else {
                dayButtons[week][daySlot].setToday(false);
                dayButtons[week][daySlot].setFont(dayButtonFont);
                dayButtons[week][daySlot].setBorderPainted(false);
            }


            daySlot = (daySlot + 1) % 7;
            if ( daySlot == 0 ) { week++; }
        }


        /* Fill Slots after last day of current month, with next month days */
        nextDay=1;
        while( week < WEEK_DISPLAY ){
          dayButtons[week][daySlot].setText( Integer.toString( nextDay ) );
          dayButtons[week][daySlot].setNextMonth(true);
          dayButtons[week][daySlot].setForeground( otherMonthForeground);
          //dayButtons[week][daySlot ].setFont( dayButtonFont );

          nextDay++;
          daySlot = ( daySlot + 1 ) % 7;
          if ( daySlot == 0 ) { week++; }
        }


        /*
         * Determine which day of last month we start to fill the slots
         * It depends of the calendar first day display choice(locale)
         * For this we set calendar to last month, retrieve the days number in
         * it and find the day to start filling the slots with.
         */

        cal.set( Calendar.MONTH , ( cal.get( Calendar.MONTH ) -1 ) );

        lastMonthDays = cal.getActualMaximum( Calendar.DATE );
        dayToStart=-1;   //initialise

        if( dayOfWeek == firstDay ){

          dayToStart = lastMonthDays - DAY_IN_WEEK +1;

        }  else if( firstDay == Calendar.SUNDAY ){

            if( dayOfWeek == Calendar.MONDAY ){
                 dayToStart = lastMonthDays;
            } else {
                dayToStart = lastMonthDays - dayOfWeek +2 ;
            }

        } else if ( firstDay == Calendar.MONDAY ){

            if( dayOfWeek == Calendar.SUNDAY ){
               dayToStart =  lastMonthDays - DAY_IN_WEEK +2;
            } else {
               dayToStart =  lastMonthDays - dayOfWeek +3;
            }
        }

        /* Fill Slots before first day with previous month last days */
        daySlot=0;
        for ( int i = dayToStart ; i <= lastMonthDays ; i++ , daySlot++ ) {
            dayButtons[0][daySlot].setText( Integer.toString(i) );
            dayButtons[0][daySlot].setLastMonth(true);
            dayButtons[0][daySlot].setForeground( otherMonthForeground );
           // dayButtons[0][daySlot ].setFont( dayButtonFont );
        }

   }



    /* Implements CalendarModelListener */
    /**
     * Invoked when an ModelChangedEvent occur.
     * Set the calendar to display with new calendar
     * and update the panel components.
     * @param e event that hold modifications information on the model calendar
     */
    public void calendarModelChanged( ModelChangedEvent e ) {
        int newMonth;       // new month to display
        int newYear;        // new year to display
        Locale oldLocale;   // oldLocale

        calToDisplay = (Calendar)e.getCalendarToDisplay().clone();
        newMonth = calToDisplay.get( Calendar.MONTH );
        newYear = calToDisplay.get( Calendar.YEAR );
        oldLocale = locale;
        locale = e.getLocale();

         //update the components only if there is a change.
        if( ( newMonth != displayedMonth ) 
                || ( newYear != displayedYear )
                || ( locale != oldLocale )){
            dayFormat.setDateFormatSymbols( new DateFormatSymbols(locale) );
            updateDays();
            updateDayNames();
        }
        
    }


    /* Inner class to listen to selected day changing  */
    private class DayListener
                  implements ItemListener {
        /* implements  ItemListener */
        /**
         * Invoked when a dayButton change state.
         * Set the selected calendar with the selected day, month year
         * @param e event holding informations
         */
        public void itemStateChanged(ItemEvent e) {
            
            Calendar cal;   // temporary calendar
            DayButton b;    // day Button

            cal = (Calendar)calToDisplay.clone();
            b = ((DayButton)e.getSource());

            if ( e.getStateChange() == ItemEvent.SELECTED) {
                if ( b.isLastMonth() ){
                    cal.add(Calendar.MONTH, -1);
                } else if ( b.isNextMonth() ){
                      cal.add(Calendar.MONTH, 1);
                }

                /* set the selected Date  */
                selectedDay = Integer.parseInt( b.getText() );
                selectedMonth = cal.get(Calendar.MONTH);
                selectedYear = cal.get(Calendar.YEAR);
                selectedCalendar.set(Calendar.YEAR, selectedYear);
                selectedCalendar.set(Calendar.MONTH, selectedMonth);
                selectedCalendar.set(Calendar.DATE, selectedDay);

                b.setSelected(true);
                if( b.isToday() ){
                    b.setBorderPainted( false);
                }
                
                fireCalendarChange();

            } else if( e.getStateChange() == ItemEvent.DESELECTED ){
                b.setContentAreaFilled( areaFilled );
                if( b.isToday() ){
                    b.setBorderPainted(true);
                }
            }
        }

    }

    /**
     * Add calendar listener. The listener will receive
     * events each time the selected date changes.
     * @param listener The calendar listener to add
     */
    protected void addCalendarListeners(CalendarListener listener){
        listenersList.add(CalendarListener.class, listener );
    }

    /**
     * Remove a calendar Listener
     * @param listener The calendar listener to remove
     */
    protected void removeListeners(CalendarListener listener){
        listenersList.remove(CalendarListener.class, listener );
    }

    /**
     * This method fire calendar change.
     * When this method is called it create a new CalendarChangedEvent
     * holding information relatives to calendar change.
     * All calendar listeners' method <code>calendarChanged</code> is invoked.
     */
    protected void fireCalendarChange(){
        CalendarListener[] listeners;   // array of Calendar Listener
        CalendarChangedEvent event;     // a calendar changed event

        listeners = listenersList.getListeners( CalendarListener.class );
        event = new CalendarChangedEvent( this, selectedCalendar );
        for(int i = listeners.length -1; i>=0; --i){
            listeners[i].calendarChanged( event );
        }
    }

    /**
     * Sets the selected date
     *
     * @param date the value of the selected date
     */
    protected void setSelectedDate(Date date){
        if( date != null){
            selectedCalendar.setTime(date);
            selectedYear = selectedCalendar.get(Calendar.YEAR);
            selectedMonth = selectedCalendar.get(Calendar.MONTH);
            selectedDay = selectedCalendar.get(Calendar.DATE);
            updateDays();
        }
    }

    /**
     * Gets the selected calendar
     * @return the selected calendar
     */
    protected Calendar getSelectedCalendar(){
        return selectedCalendar;
    }

    /**
     * Gets the background colour of the day labels.
     * @return the day labels background colour.
     */
    protected Color getDayLabelBackground(){
        return dayLabelBackground;
    }

    /**
     * Gets the foreground colour of the day labels.
     * @return the day labels colour.
     */
    protected Color getDaylabelForeground(){
        return dayLabelForeground;
    }

    /**
     * Gets the font of the day labels.
     * @return the day labels' font.
     */
    protected Font getDayLabelFont(){
        return dayLabelFont;
    }

    /**
     * Returns the border of the day labels.
     * @return the border object for the day labels.
     */
    protected Border getDayLabelBorder(){
        return dayLabelBorder;
    }


    /**
     * Gets the font of days buttons.
     * @return days buttons font.
     */
    protected Font getButtonFont() {
       return dayButtonFont;
    }

    /**
     * Gets the font of today button.
     * @return today button font.
     */
    protected Font getTodayButtonFont(){
        return todayButtonFont;
    }

    /**
     * Gets the foreground colour of the day buttons that are
     * not current month.
     * @return the foreground colour of the day buttons that are
     *          not current month.
     */
    protected Color getOtherMonthButtonForeground(){
       return otherMonthForeground;
    }

    /**
     * Gets the foreground colour of the day buttons that are
     * current month.
     * @return the foreground colour of the day buttons that are
     *         current month.
     */
    protected Color getCurrentMonthButtonForeground(){
       return currentMonthForeground;
    }

    /**
     * Sets the background colour of the day labels.
     * @param color the colour to become the background colour.
     */
    protected void setDayLabelBackground( Color color ) {
        dayLabelBackground = color;
        for( JLabel dayLabel: dayLabels){
            dayLabel.setBackground( dayLabelBackground );
        }
    }
    
    /**
     * Sets the foreground colour of the day labels.
     * @param color the colour to become the foreground colour.
     */
    protected void setDayLabelForeground( Color color ) {
        dayLabelForeground = color;
        for( JLabel dayLabel: dayLabels){
            dayLabel.setForeground( dayLabelForeground );
        }
    }

    /**
     * Sets the font of day labels.
     * @param font the font to become day labels' font
     */
    protected void setDayLabelFont( Font font ){
        dayLabelFont = font;
        for( JLabel dayLabel: dayLabels){
            dayLabel.setFont( dayLabelFont );
        }
    }

    /**
     * Sets the border of day labels.
     * @param border  the border to be rendered for day labels
     */
    protected void setDayLabelBorder( Border border ) {
        dayLabelBorder = border;
         for( JLabel dayLabel: dayLabels){
            dayLabel.setBorder( dayLabelBorder );
        }
    }

    /**
     * Sets the font of days buttons.
     * @param font  the font to become day buttons font
     */
    protected void setButtonFont( Font font ) {
       dayButtonFont = font;
       for( DayButton[] Arr : dayButtons  ){
           for( DayButton dayButton : Arr ) {
               if( ! dayButton.isToday() ) {
                   dayButton.setFont( dayButtonFont );
               }
           }
       }
    }

    /**
     * Sets the font of today button.
     * @param font the font to become today button font
     */
    protected void setTodayButtonFont( Font font) {
       todayButtonFont = font;
        for( DayButton[] Arr : dayButtons  ){
           for( DayButton dayButton : Arr ) {
               // set the font for today button and exit the loop
               if( dayButton.isToday() ) {
                   dayButton.setFont( todayButtonFont );
                   break;
               }
           }
        }
    }

    /**
     * Sets the the foreground colour of day buttons that are
     * not current month.
     * @param color the colour to become the foreground colour.
     */
    protected void setOtherMonthButtonForeground( Color color ){
       otherMonthForeground = color;
        for( DayButton[] Arr : dayButtons  ){
           for( DayButton dayButton : Arr ) {
               // set the color for all button except currenth month button
               if( dayButton.isNextMonth() || dayButton.isLastMonth()
                       && ! dayButton.isCurrentMonth() ) {
                   dayButton.setForeground( otherMonthForeground );
               }
           }
        }
    }

    /**
     * Sets the the foreground colour of day buttons that are
     * current month.
     * @param color the colour to become the foreground colour.
     */
    protected void setCurrentMonthButtonForeground( Color color ){
      currentMonthForeground =  color;
       for( DayButton[] Arr : dayButtons  ){
           for( DayButton dayButton : Arr ) {
               // set the color for all button of currenth month button
               if(  dayButton.isCurrentMonth() ) {
                   dayButton.setForeground( currentMonthForeground );
               }
           }
        }

   }

    /**
     * Sets the contentAreaFilled property of day buttons.
     * If <code>true</code> buttons will paint the content area.
     * <p>
     * The default value is  <code>false </code> and become <code>true</code>
     * on mouse over, So the content is filled on mouse over.
     * Setting the property to <code>true</code> will reverse this.
     * @param b  if true, the content should be filled
     *              and become not  filled on mouse over;
     *           if false the content area is not filled
     *              and become filled on mouse over
     */
    protected void setButtonAreaFilled(boolean b){
        areaFilled = b;
        for( DayButton[] Arr : dayButtons  ){
           for( DayButton dayButton : Arr ) {
               // set the color for all button of currenth month button
               if(  dayButton.isSelected() ) {
                   dayButton.setContentAreaFilled( ! areaFilled );
               } else {
                   dayButton.setContentAreaFilled( areaFilled );
               }
           }
        }

    }

   


}
